/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
 * Copyright (c) 2011 SMUNIX
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Author: Providence M. Salumu <providence.salumu@smunix.com>
 */


#ifndef INJECTABLE_HPP__073048
#define INJECTABLE_HPP__073048

#include "singleton.hpp"
#include <set>
#include <mutex>
#include <type_traits>

namespace smunix
{
  // template<class T, class IsInjectableOrNot> class Injector;

  /**
   * An Injectable object is supposed to be acced by pointer only.
   * Its sole purpose is to dispatch one object into Aggregators.
   * You have to take heed to the fact that after calling the DispatchAndInject ()
   * method of an Injectable, all the aggregators that hasn't never
   * been filled before will be filled.
   *
   * Here goes a simple use case:
   *
   * struct Configuration;
   * struct Configurable
   * {
   *   virtual ~Configurable (void) {}
   *   virtual void Configure (Configuration const &) = 0;
   * };
   *
   * struct Configuration : public Injectable<Configuration>
   * {
   *   virtual ~Configuration (void) {}
   *   void SetBuildTime (int a) {}
   *   int GetBuildTime (void) const {}
   * };
   *
   * struct Motor : public Configurable
   * {
   *   virtual ~Motor (void) {}
   *   virtual void Configure (Configuration const &) {}
   * };
   *
   * struct Engine
   * {};
   *
   * struct ConfigurableEngine : public Aggregator<ConfigurableEngine, Configurable, Engine, Configuration>
   * {
   * };
   *
   * typedef Frame<ConfigurableEngine, Motor, Configuration> ConfigurableMotorEngine;
   *
   * Configuration::SP conf1 = Configuration::Create ();
   * Configuration::SP conf2 = Configuration::Create ();
   * conf1->SetBuildTime (0);
   * conf2->SetBuildTime (2);
   *
   * int main (void)
   * {
   *   Configuration::SP conf1 = Configuration::Create ();
   *   Configuration::SP conf2 = Configuration::Create ();
   *   ConfigurableMotorEngine eng;
   *   ConfigurableEngine cfgEngine;
   *
   *   cfgEngine.Pack<Configurable> (new Motor);
   *   cfgEngine.Pack<Engine> (new Engine);
   *   conf1->SetBuildTime (1);
   *   conf2->SetBuildTime (2);
   *   // conf1 is the best fit, so let's use it!
   *   conf2->DispatchAndInject ();
   *   ASSERT_CHECK_EQUAL (cfgEngine.Peek<Configuration> ().GetBuildTime (), 2);
   *   ASSERT_CHECK_EQUAL (eng.Peek<Configuration> ().GetBuildTime (), 2);
   *   ConfigurableEngine cfgEngineMars;
   *   conf1->DispatchAndInject ();
   *   ASSERT_CHECK_EQUAL (cfgEngineMars.Peek<Configuration> ().GetBuildTime (), 1);
   *   ASSERT_CHECK_EQUAL (eng.Peek<Configuration> ().GetBuildTime (), 2);
   *   ASSERT_CHECK_EQUAL (cfgEngine.Peek<Configuration> ().GetBuildTime (), 2);
   *
   *   return 0;
   * }
   */

  template<class T>
  class Injectable : virtual public std::enable_shared_from_this<T>
  {
  public:
    typedef T Type;
    typedef std::shared_ptr<Type> SP;
    typedef Injectable<T> ThisType;
    template<class, class>
    friend class Injector;
  public:
    static SP Create (void)
    {
      return SP (new Type);
    }
    void DispatchAndInject (void);
  protected:
    Injectable (void) {}
    virtual ~Injectable (void) {}
  };

  template<class T>
  struct IsInjectable : public std::is_base_of<Injectable<T>, T >
  {};

  template<class T>
  class Aggregated;

  template<class T, class IsInjectableOrNot = typename IsInjectable<T>::type >
  class Injector : public Singleton<Injector<T, IsInjectableOrNot> >
  {
    typedef Injectable<T> InjectableType;
    typedef typename InjectableType::SP SP;
    typedef Aggregated<T> * AggregatedRawPtr;
    typedef std::set<AggregatedRawPtr> ObjectRawPtrSet;
    typedef std::mutex Mutex;
    typedef std::lock_guard<Mutex> Lock;
  public:
    void Dispatch (SP i);
    void Add (AggregatedRawPtr object)
    {
      Lock lock (mMutex);
      mObjects.insert (object);
    }
    void Clean (AggregatedRawPtr object)
    {
      Lock lock (mMutex);
      mObjects.erase (object);
    }
  private:
    Mutex mMutex;
    ObjectRawPtrSet mObjects;
  };

  template<class T>
  class Injector<T, std::false_type> : public Singleton<Injector<T, std::false_type> >
  {
    typedef Injectable<T> InjectableType;
    typedef typename InjectableType::SP SP;
    typedef Aggregated<T> * AggregatedRawPtr;
    typedef std::set<AggregatedRawPtr> ObjectRawPtrSet;
    typedef std::mutex Mutex;
    typedef std::lock_guard<Mutex> Lock;
  public:
    void Dispatch (SP i) {}
    void Add (AggregatedRawPtr object) {}
    void Clean (AggregatedRawPtr object) {}
  };

  template<class T>
  void Injectable<T>::DispatchAndInject (void)
  {
    typedef Injector<T> InjectorType;
    InjectorType::Instance ().Dispatch (this->shared_from_this ());
  }
} // namespace smunix

#include "details/injectable.hpp"

#endif /* INJECTABLE_HPP__073048 */
